<?php
if(!interface_exists('XDBDriver', false)){
	require(XFRAMEWORK_DIR.'/module/db/driver/XDBDriver.php');
}
class XMongoDriver implements XDBDriver{
	protected $name = null;
	protected $extension = 'mongo';
	protected $config = array();
	protected $connection = null;
	protected $database = null;
	protected $connected = false;
	protected $host = null;
	protected $port = null;	
	protected $collections = array();

	public function __construct($name, $config){
		$this->name = $name;
		if(!$this->extension
			|| !extension_loaded($this->extension)){
			throw new XORMException("Cannot load {$this->extension} extension. Please check your PHP configuration.");
		}
		$this->config = $config;
		if(!is_array($this->config)
			|| !isset($this->config['host'])
			|| !isset($this->config['database'])){
			throw new XORMException("Cannot load {$this->extension} configuration.");
		}
	}
	
	public function name(){
		return $this->name;
	}
	
	public function connect(){
		if($this->connected
			&& $this->connection){
			return $this->connection;
		}			
		$this->host = $this->config['host'];
		$this->port = isset($this->config['port']) ? $this->config['port'] : 27017;
		$this->connection = new Mongo("mongodb://{$this->host}:{$this->port}", array(
			"connect" => false,
			"persist" => isset($this->config['persist']) ? $this->config['persist'] : '',
		));
		$this->connected = $this->connection->connect();
		if(!$this->connected){
			throw new XORMException("can't connect mongo server mongodb://{$this->host}:{$this->port}");
		}
		$this->database = $this->connection->selectDB($this->config['database']);
		if(!$this->database){
			throw new XORMException("can't connect mongo database {$this->config['database']} on server mongodb://{$this->host}:{$this->port}");
		}
		$this->_handleError();
		XEventContext::singleton()->publish(new XLoggingEvent($this, INFO, "Mongo server mongodb://{$this->host}:{$this->port} connected."));
		$this->connected = true;
		return $this->connection;
	}
	
	public function close(){
		$this->connected = !$this->connection->close();
		if(!$this->connected){
			$this->connection = null;
			$this->database = null;
		}
		XEventContext::singleton()->publish(new XLoggingEvent($this, INFO, "Mongo server mongodb://{$this->host}:{$this->port} closed."));
		return !$this->connected;
	}
	
	public function exec($sql){
		$result = $this->database()->execute($sql);
		if(!isset($result['ok'])
			|| $result['ok'] != 1){
			return false;
		}
		return true;
	}
	
	public function query($sql){
		return $this->database()->execute($sql);
	}
	
	public function lastId(){
		
	}
	
	public function escape($str){
		return $str;
	}
	
	public function offsetKeyword(){
		
	}
	
	public function sizeKeyword(){
		
	}
	
	public function error(){
		$error = $this->database()->lastError();
		if(!$error 
			|| !isset($error["err"])){
			return null;
		}
		return $error["err"];
	}
	
	public function hasError(){
		$error = $this->database()->lastError();
		if(!$error 
			|| !isset($error["err"])
			|| !$error["err"]){
			return false;
		}
		return true;		
	}
	
	private function _handleError(){
		$error = $this->error();
		if($error){
			$this->database()->resetError();
			throw new XORMException($error);
		}
	}
	
	public function version(){
		$result = $this->database()->execute("function() { return db.version(); }");
		if(!isset($result['retval'])){
			return null;
		}
		return $result['retval'];
	}
	
	public function loadTable($tableName){
		$table = new XBasicTable();
		$table->setName($tableName);
		$schema = XConfig::singleton()->get("project.mongo.schema.{$tableName}");
		if($schema
			&& is_array($schema)){			
			$table->setColumns($schema);
		}
		return $table;
	}
	
	public function insert(XTable $table, XModel $model){
		$attrs = array();
		foreach($table->columns() as $key => $column){
			$value = $model->get($key);
			if(!is_null($value)){
				$attrs[$key] = $value;
			}
		}
		return $this->collection($table)->insert($attrs);
	}
	
	public function update(XTable $table, XModel $model){
		$pk = $model->get($table->PK());
		if(is_null($pk)){
			throw new XORMException("the primary key of model ".get_class($model)." is null.");
		}
		$attrs = array();
		foreach($table->columns() as $key => $column){
			$value = $model->get($key);
			if(!is_null($value)){
				$attrs[$key] = $value;
			}
		}
		unset($attrs[$table->PK()]);
		return $this->collection($table)->update(array(
			$table->PK() => $pk,
		), array('$set' => $attrs));
	}
	
	public function remove(XTable $table, XModel $model){
		$pk = $model->get($table->PK());
		if(is_null($pk)){
			throw new XORMException("the primary key of model ".get_class($model)." is null.");
		}
		return $this->collection($table)->remove(array(
			$table->PK() => $pk,
		), array(
			"justOne" => true,
		));
	}
	
	public function queryCriteria(XTable $table, XCriteria $criteria){
		$translator = new XMongoCriteriaTranslator();
		$translator->setDriver($this);
		$cursor = $translator->translate($table, $criteria);
		if(!is_object($cursor)){
			return $cursor;
		}
		$results = array();
		while($cursor->hasNext()){
			$results[] = $cursor->getNext();
		}
		return $results;
	}
		
	public function database(){
		if(!$this->connected
			|| !$this->connection
			|| !$this->database){
			$this->connect();
		}
		return $this->database;
	}
	
	public function collection(XTable $table){
		$tableName = $table->name();
		if(isset($this->collections[$tableName])){
			return $this->collections[$tableName];
		}
		return $this->database()->selectCollection($tableName);
	}
	
}
?>